#!/usr/bin/env python
# -*- coding: latin-1 -*-


from climber import Climber
from slope import Slope
import os, time
from logging import error, info

from xml.etree import ElementTree as ET

from backup import add_subelement

def doctest():
	import doctest
	doctest.testmod()

RAISE_EXCEPTION = True


class ContestException(Exception):
	def __init__(self, message):
		self.message = message
	def __str__(self):
		return repr(self.message)

def raise_exception(message):
	if RAISE_EXCEPTION:
		raise ContestException(message)
	else:
		error(message)
	

class Contest(object):
	"""Object which represent the contest"""
	def __init__(self):
		super(Contest, self).__init__()
			
		self._climbers = {}
		self._slopes = {}
		self.categories = []
		self.contest_db_filename = os.path.abspath("contest.xml")
		self.xml_root = ET.Element("xml")
		
		self._finalist_climbers = {}
		self._final_slopes = {}
		self.final_started = False
	
	def _get_climbers(self):
		if self.final_started:
			return self._finalist_climbers
		else:
			return self._climbers
	climbers = property(fget=_get_climbers)
		
	def _get_slopes(self):
		if self.final_started:
			return self._final_slopes
		else:
			return self._slopes
	slopes = property(fget=_get_slopes)
		
	def add_category(self, category_name):
		"""
		>>> contest = Contest()
		>>> contest.add_category('men')
		True
		>>> contest.add_category('men')
		False
		"""
		if (category_name != None):
			if category_name not in self.categories:
				self.categories.append(category_name)
				return True
			else:
				raise_exception('Une catégorie avec le nom: %s existe déjà.' % category_name)
				return False
		else:
			raise_exception('Il faut un nom pour ajouter une catégorie')
			return False
			
	def remove_category(self, category_name):
		"""
		>>> contest = Contest()
		>>> contest.remove_category(None)
		False
		>>> contest.remove_category('men')
		False
		>>> contest.add_category('men')
		True
		>>> contest.remove_category('men')
		True
		"""
		if (category_name != None):
			if category_name in self.categories:
				self.categories.remove(category_name)
				for climber_id in self.climbers.keys():
					climber = self.climbers[climber_id]
					if climber.category == category_name:
						climber.category = ''
				return True
			else:
				raise_exception("La catégorie %s n'existe pas.\n Impossible de la supprimer." % category_name)
				return False
		else:
			raise_exception('Impossible de supprimer une catégorie sans nom')
			return False
	
	def add_climber(self, climber):
		"""
		>>> contest = Contest()
		>>> climber = Climber('bob', 'leponge', 0)
		>>> contest.add_climber(climber)
		True
		>>> len(contest.climbers)
		1
		>>> contest.add_climber(climber)
		False
		>>> len(contest.climbers)
		1
		"""
		if isinstance(climber, Climber):
			if climber.id not in self.climbers.keys():
				self.climbers[climber.id] = climber
				try:
					self.add_category(climber.category)
				except:
					pass
				return True
			else:
				raise_exception('Un grimpeur a déjà le numéro: %i.' % climber.id)
				return False
		else:
			raise_exception('Attention gros gros problème')
			return False
		
	def remove_climber(self, climberid):
		"""
		>>> contest = Contest()
		>>> climber = Climber('bob', 'leponge', 0)
		>>> contest.add_climber(climber)
		True
		>>> contest.remove_climber(2)
		False
		>>> len(contest.climbers)
		1
		>>> contest.remove_climber(climber.id)
		True
		>>> len(contest.climbers)
		0
		"""
		if climberid in self.climbers.keys():
			del self.climbers[climberid]
			return True
		else:
			raise_exception("Aucun grimpeur n'a le numéro %i.\n Impossible de le supprimer." % climberid)
			return False
	
	def add_slope(self, slope):
		"""
		>>> contest = Contest()
		>>> slope = Slope(0)
		>>> contest.add_slope(slope)
		True
		>>> len(contest.slopes)
		1
		>>> contest.add_slope(slope)
		False
		>>> len(contest.slopes)
		1
		"""
		if isinstance(slope, Slope):
			if slope.id not in self.slopes.keys():
				self.slopes[slope.id] = slope
				return True
			else:
				raise_exception('Un bloc a déjà le numéro: %i.' % slope.id)
				return False
		else:
			return False
	
	def remove_slope(self, slope_id):
		"""
		>>> contest = Contest()
		>>> slope = Slope(0)
		>>> contest.add_slope(slope)
		True
		>>> contest.remove_slope(2)
		False
		>>> len(contest.slopes)
		1
		>>> contest.remove_slope(0)
		True
		>>> len(contest.slopes)
		0
		"""
		if slope_id in self.slopes.keys():
			del self.slopes[slope_id]
			for climber_id in self.climbers.keys():
				climber = self.climbers[climber_id]
				if slope_id in climber.climbed_slopes:
					climber.remove_climbed_slope(slope_id)
			return True
		else:
			raise_exception("Aucun bloc n'a le numéro %i does not exist.\n impossible de le supprimer." % slopei_d)
			return False
		
	def get_slopes(self):
		"""
		>>> contest = Contest()
		>>> slope = Slope(0)
		>>> contest.get_slopes()
		[]
		>>> contest.add_slope(slope)
		True
		>>> contest.get_slopes()
		[(0, 0)]
		>>> slope = Slope(1)
		>>> contest.add_slope(slope)
		True
		>>> contest.get_slopes()
		[(0, 0), (1, 0)]
		"""
		slopes = []
		for slope_id in self.slopes.keys():
			slope = self.slopes[slope_id]
			slopes.append(
			(
			slope.id,
			slope.successfull_climbings,
			)
			)
		return slopes
	
	
	def add_climbing(self, climber_id, slope_id):
		
		if climber_id in self.climbers:
			if  slope_id in self.slopes:
				climber = self.climbers[climber_id]
				slope =	self.slopes[slope_id]
				if climber.add_climbed_slope(slope):
					slope.raise_climbings()
					return True
				else:
					raise_exception('Le grimpeur numéro %i a déjà grimpé le bloc numéro %i' % (climber_id, slope_id))
					return False
			else:
				raise_exception("Aucun bloc n'a le numéro %i " % slope_id)
				return False
		else:
			raise_exception("Aucun grimpeur n'a le numéro %i " % climber_id)
			return False
	
	def remove_climbing(self, climber_id, slope_id):
		if climber_id in self.climbers:
			if slope_id in self.slopes:
				climber = self.climbers[climber_id]
				slope = self.slopes[slope_id]
				if climber.remove_climbed_slope(slope_id):
					slope.drop_climbings()
					return True
				else:
					raise_exception("Le grimpeur numéro %i n'a pas encore grimpé le bloc numéro %i" % (climber_id, slope_id))
					return False
			else:
				raise_exception("Aucun bloc n'a le numéro %i " % slope_id)
				return False
		else:
			raise_exception("Aucun grimpeur n'a le numéro %i " % climber_id)
			return False
	
	def get_climbers_id(self):
		return [self.climbers[climber_key].id for climber_key in self.climbers.keys()]
	
	def get_slopes_id(self):
		return [self.slopes[slope_key].id for slope_key in self.slopes.keys()]
	
	def start_final(self, nbclimbers_by_category):
		"""
		>>> compet = Contest()
		>>> compet.add_category('elite')
		True
		>>> compet.add_category('normal')
		True
		>>> for i in range(4):
		... 		slope = Slope(i)
		...		if i % 2 == 0:
		...			cat = 'elite'
		...		else:
		...			cat = 'normal'
		... 		climber = Climber("name%s" % i, "name%s" % i, i, category=cat)
		... 		compet.add_slope(slope)
		... 		compet.add_climber(climber)
		True
		True
		True
		True
		True
		True
		True
		True
		>>> compet.add_climbing(0, 0)
		True
		>>> compet.add_climbing(0, 1)
		True
		>>> compet.add_climbing(1, 0)
		True
		>>> compet.add_climbing(1, 1)
		True
		>>> compet.add_climbing(3, 2)
		True
		>>> compet.add_climbing(3, 3)
		True
		>>> compet.get_rankings_in_category('elite')[0][2]
		'name0'
		>>> compet.get_rankings_in_category('normal')[0][2]
		'name3'
		>>> compet.start_final({'elite': 2})
		True
		>>> slope = Slope(0)
		>>> compet.add_slope(slope)
		True
		>>> compet.add_climbing(2, 0)
		True
		>>> compet.get_rankings_in_category('elite')[0][2]
		'name2'
		"""
		self._finalist_climbers = {}
		for category in nbclimbers_by_category.keys():
			nbclimbers = nbclimbers_by_category[category]
			ranking = self.get_rankings_in_category(category)
			for i in range(nbclimbers):
				climber_id = ranking[i][1]
				climber = self._climbers[climber_id]
				climber.finalist = True
				self._finalist_climbers[climber_id] = climber
		self.final_started = True
		return True
	
	def _get_ranking(self, climbers):
		if isinstance(climbers, dict):
			climbers = [climbers[key] for key in climbers.keys()]
		score_index = int(self.final_started)
		scores = [climber.get_score()[score_index] for climber in climbers]
		unranked_climbers = [climber for climber in climbers]
		
		n_scores = len(scores)
		i_scores = range(n_scores)
		# i_scores will contain the list of climber index sorted by top score
		i_scores.sort(reverse=True, key=lambda j : scores[j])

		ranking =[
				(
				i_scores.index(index)+1,
				unranked_climbers[index].id,
				unranked_climbers[index].name + ' ' + unranked_climbers[index].fullname,
				unranked_climbers[index].category,
				scores[index]
				) for index in i_scores
			]
		return ranking
		
	
	def get_rankings(self):
		"""
		>>> compet = Contest()
		>>> for i in range(4):
		... 		slope = Slope(i)
		... 		climber = Climber("name%s" % i, "name%s" % i, i)
		... 		compet.add_slope(slope)
		... 		compet.add_climber(climber)
		True
		True
		True
		True
		True
		True
		True
		True
		>>> compet.add_climbing(0, 0)
		True
		>>> compet.get_rankings()[0][2]
		'name0'
		>>> compet.add_climbing(0, 0)
		False
		>>> compet.add_climbing(1, 0)
		True
		>>> compet.add_climbing(1, 1)
		True
		>>> compet.get_rankings()[0][2]
		'name1'
		>>> from slope import SLOPE_INITIAL_VALUE
		>>> compet.slopes[0].get_value() == SLOPE_INITIAL_VALUE/2
		True
		>>> compet.remove_climbing(1, 0)
		True
		>>> compet.remove_climbing(1, 1)
		True
		>>> compet.get_rankings()[0][2]
		'name0'
		>>> compet.slopes[0].get_value() == SLOPE_INITIAL_VALUE
		True
		"""
		return self._get_ranking(self.climbers)
	
	def get_rankings_in_category(self, category_name):
		"""
		>>> compet = Contest()
		>>> compet.add_category('elite')
		True
		>>> compet.add_category('normal')
		True
		>>> for i in range(4):
		... 		slope = Slope(i)
		...		if i % 2 == 0:
		...			cat = 'elite'
		...		else:
		...			cat = 'normal'
		... 		climber = Climber("name%s" % i, "name%s" % i, i, category=cat)
		... 		compet.add_slope(slope)
		... 		compet.add_climber(climber)
		True
		True
		True
		True
		True
		True
		True
		True
		>>> compet.add_climbing(0, 0)
		True
		>>> compet.add_climbing(0, 1)
		True
		>>> compet.add_climbing(1, 0)
		True
		>>> compet.add_climbing(1, 1)
		True
		>>> compet.add_climbing(3, 2)
		True
		>>> compet.add_climbing(3, 3)
		True
		>>> compet.get_rankings_in_category('elite')[0][2]
		'name0'
		>>> compet.get_rankings_in_category('normal')[0][2]
		'name3'
		"""
		if category_name in self.categories:
			climbers = [self.climbers[key] for key in self.climbers.keys() if self.climbers[key].category == category_name]
			return self._get_ranking(climbers)
		else:
			return []
	
	def save(self):
		"""
		>>> compet = Contest()
		>>> for i in range(4):
		... 		slope = Slope(i)
		... 		climber = Climber("name%s" % i, "name%s" % i, i)
		... 		compet.add_slope(slope)
		... 		compet.add_climber(climber)
		True
		True
		True
		True
		True
		True
		True
		True
		>>> compet.add_climbing(0, 0)
		True
		>>> compet.add_climbing(1, 0)
		True
		>>> compet.add_climbing(1, 1)
		True
		>>> compet.save()
		"""
		self.xml_root = ET.Element("xml")
		version_node = add_subelement(self.xml_root, "version", None)
		final_started = self.final_started
	
	
		self.final_started = False
		climbers_node = add_subelement(version_node, "climbers", None)
		for climber_id in self.climbers.keys():
			climber = self.climbers[climber_id]
			climber.save(climbers_node)

		slopes_node = add_subelement(version_node, "slopes", None)
		for slope_id in self.slopes.keys():
			slope = self.slopes[slope_id]
			slope.save(slopes_node)
		self.final_started = True
		climbers_node = add_subelement(version_node, "finalclimbers", None)
		for climber_id in self.climbers.keys():
			climber = self.climbers[climber_id]
			climber.save(climbers_node)

		slopes_node = add_subelement(version_node, "finalslopes", None)
		for slope_id in self.slopes.keys():
			slope = self.slopes[slope_id]
			slope.save(slopes_node)		
	
		self.final_started = final_started
		add_subelement(version_node, "finalstarted", int(self.final_started))
		tree = ET.ElementTree(self.xml_root)
		tree.write(os.path.join(self.contest_db_filename))
		del self.xml_root
		
	def load_climber(self, xml_node):
		id = int(xml_node.attrib['id'])
		
		name = ''
		fullname = ''
		category = None
		age = None
		sexe = None
		if xml_node.find('name').text:
			name = xml_node.find('name').text
		if xml_node.find('fullname').text:
			fullname = xml_node.find('fullname').text
		if xml_node.find('age').text:
			age = int(xml_node.find('age').text)
		if xml_node.find('sexe').text:
			sexe = str(xml_node.find('sexe').text)
		if xml_node.find('category').text:
			category = str(xml_node.find('category').text)
		if xml_node.find('finalist').text:
			finalist = bool(int(xml_node.find('finalist').text))
			
		climber = Climber(name, fullname, id, age, sexe, category)
		climber.finalist = finalist
		for climbed_node in xml_node.find('climbedslopes'):
			slope_id = int(climbed_node.attrib['id'])
			if slope_id in self.slopes.keys():
				slope = self.slopes[slope_id]
				climber.add_climbed_slope(slope)
				slope.raise_climbings()
		
		return climber
		
	def load_slope(self, xml_node):
		id = int(xml_node.attrib['id'])
		#name = str(xml_node.find('name').text)
		initial_value = int(xml_node.find('initialvalue').text)
		return Slope(id, initial_value)#, name)
	
	def load(self, clean_before_loading):
		"""
		>>> compet = Contest()
		>>> for i in range(4):
		... 		slope = Slope(i)
		... 		climber = Climber("name%s" % i, "name%s" % i, i)
		... 		compet.add_slope(slope)
		... 		compet.add_climber(climber)
		True
		True
		True
		True
		True
		True
		True
		True
		>>> compet.add_climbing(0, 0)
		True
		>>> compet.add_climbing(1, 0)
		True
		>>> compet.add_climbing(1, 1)
		True
		>>> compet.save()
		>>> del compet
		>>> compet = Contest()
		>>> compet.load()
		>>> compet.get_rankings()[0][2]
		'name1'
		"""
		tree = ET.parse(self.contest_db_filename)
		xml_root = tree.getroot()
		version_node = xml_root.find('version')
	
		if clean_before_loading:
			self._climbers = {}
			self._slopes = {}
			self.categories = []
			self._finalist_climbers = {}
			self._final_slopes = {}
	
		self.final_started = False
		for slope_node in version_node.find('slopes'):
			slope = self.load_slope(slope_node)
			self.slopes[slope.id] = slope
		for climber_node in version_node.find('climbers'):
			climber = self.load_climber(climber_node)
			self.climbers[climber.id] = climber
			if not climber.category in self.categories:
				self.categories.append(climber.category)
		self.final_started = True	
		for slope_node in version_node.find('finalslopes'):
			slope = self.load_slope(slope_node)
			self.slopes[slope.id] = slope
		for climber_node in version_node.find('finalclimbers'):
			climber = self.load_climber(climber_node)
			self.climbers[climber.id] = climber
			if not climber.category in self.categories:
				self.categories.append(climber.category)
		self.final_started = bool(int(version_node.find('finalstarted').text))
			
	def load_from_csv(self, filename, clean_before_loading=True):
		import csv
		if clean_before_loading:
			self._climbers = {}
			self._slopes = {}
			self.categories = []
			self._finalist_climbers = {}
			self._final_slopes = {}
		if os.path.exists(filename):
			reader = csv.reader(open(filename, "rb"), delimiter=',')
			numero = 3
			reader.next()
			reader.next()
			for row in reader:
				if row[3] == 'X':
					category = 'Enfant'
					sexe = ''
				if row[4] == 'X':
					category = 'Femme'
					sexe = 'Femme'
				if row[5] == 'X':
					category = 'Femme'
					sexe = 'Femme'
				if row[6] == 'X':
					category = 'Pop. homme'
					sexe = 'Homme'
				if row[7] == 'X':
					category = 'Elite homme'
					sexe = 'Homme'
				if row[8] == 'X':
					category = 'Pop. homme'
					sexe = ''
				try:
					age = int(row[2])
				except:
					age = 0
				climber = Climber(unicode(row[1], 'utf-8'), unicode(row[0], 'utf-8'), int(numero), age, sexe=sexe, 
								 category=category)
				self.add_climber(climber)
				numero += 1
		
if __name__ == '__main__':
	contest = Contest()
	contest.load_from_csv('inscriptions.csv')
	for climber in [contest.climbers[key] for key in contest.climbers]:
		print climber.fullname
	contest.save()

    
